package chloroform.json

import chloroform.l10n.ProjectLocale
import scaladuct.reflect.CookieReflection
import scaladuct.{CompositeCookieBean, PropertyValue, Property, ConfigPageGateway}
import collection.immutable.List
import argo.jdom._
import chloroform.json.comp._
import chloroform._

/**
 * User: zslajchrt
 * Date: 1.5.11
 * Time: 11:26
 */

object ConfigSessionHandlerImp extends ConfigSessionHandlerImp

class ConfigSessionHandlerImp extends CompositeJsonCookieHandler {

  val constructors = ComponentHandlers.getConstructors

  val updaters = ComponentHandlers.getUpdaters

  val hintConstructors = ComponentHandlers.getHintConstructors

  val hintUpdaters = ComponentHandlers.getHintUpdaters

  val projectEntryConstructors = ComponentHandlers.getProjectEntryConstructors


  val constructorsExt = Lists.getConstructors ::: Mementos.getConstructors ::: Metadata.getConstructors :::
    Validation.getConstructors ::: References.getConstructors

  val updatersExt = Nil

  val hintConstructorsExt = Metadata.getHintConstructors

  val hintUpdatersExt = Nil

  val projectEntryConstructorsExt = Metadata.getProjectEntryConstructors

  class ElementRepresentationAssembler[E, C, R](baseElementHandlers: List[GenericFactoryForElement[E, C, R]],
                                                extElementHandlers: List[GenericFactoryForElement[E, C, R]],
                                                ctx: C,
                                                builderCallBack: (E, List[R]) => Unit
                                                 )
    extends Function1[E, Unit] {

    def apply(elem: E) {
      var productParts: List[R] = Nil

      // find the first base element handler - there can be only one base element handler
      baseElementHandlers.find(baseElementHandler => {
        baseElementHandler.create(elem, ctx) match {
          case None => false
          case Some(prodPart) => {
            productParts ::= prodPart
            true
          }
        }
      }) match {
        case None =>
          error("No factory for element " + elem + " (factories: " + baseElementHandlers + ")")
        case Some(bld) => // ok
      }

      // loop through the extensions
      extElementHandlers.foreach(extHandler => {
        extHandler.create(elem, ctx) match {
          case None =>
          case Some(prodPart) => productParts ::= prodPart
        }
      })

      builderCallBack(elem, productParts)

    }

  }

  class PropertiesFilter(ctx: CookiePropertyProcessingContext) extends Function1[PropertyValue[_], Boolean] {
    def apply(prop: PropertyValue[_]) = {
      if (prop.refTypeInfo != null &&
        prop.refTypeInfo.name != null &&
        prop.refTypeInfo.name != "")
        false
      else {
        ctx.cookieGateway.pageExtension(classOf[CookieReflection]) match {
          case None => true
          case Some(cookieRefl) => cookieRefl.propertyInfo(prop).enabled
        }
      }
    }
  }

  def getEndBuilder = (new End).createBuilder().get

  def getResultBuilder(gateway: ConfigPageGateway[_]) = {
    val context: GatewayContext = gateway.asInstanceOf[GatewayContext]
    new Result(new EditContext(context.contextName, gateway.id, gateway.canUnconsume()),
      "" + context.instance).createBuilder().get
  }

  def updateHints(gateway: ConfigPageGateway[_], selectedHint: Property[Nothing], newValue: JsonNode): Property[AnyRef] = {
    val ctx = new CookiePropertyProcessingContext(gateway, gateway.asInstanceOf[GatewayContext].locale);

    var hintJsonBuilder: CompositeJsonBuilderFactory = null
    val reprAsm = new ElementRepresentationAssembler[Property[Nothing], CookiePropertyProcessingContext, PropertyUpdater[AnyRef]](
      hintUpdaters, hintUpdatersExt, ctx, (property, updaters) => {
        require(updaters.size == 1, "Expected exactly one updater")
        val hintUpdater: PropertyUpdater[_] = updaters(0)
        hintUpdater.updateProperty(newValue)
      })
    reprAsm.apply(selectedHint)

    selectedHint

  }

  def getHintsBuilder(gateway: ConfigPageGateway[_], hints: List[Property[Nothing]]): JsonNodeBuilder[JsonRootNode] = {
    val ctx = new CookiePropertyProcessingContext(gateway, gateway.asInstanceOf[GatewayContext].locale);

    var hintBuilders: List[JsonBuilderFactory[JsonRootNode]] = Nil
    val reprAsm = new ElementRepresentationAssembler[Property[Nothing], CookiePropertyProcessingContext, JsonBuilderFactory[JsonRootNode]](
      hintConstructors, hintConstructorsExt, ctx, (hint, hintPartBuilders) => {
        hintBuilders ::= new CompositeJsonBuilderFactory(hintPartBuilders)
      })

    hints.foreach(reprAsm)

    new Hints(new EditContext(gateway.asInstanceOf[GatewayContext].contextName, gateway.id,
      gateway.canUnconsume()), hintBuilders).createBuilder().get
  }

  def updateCookie(gateway: ConfigPageGateway[_], updatedNodesMap: java.util.Map[JsonStringNode, JsonNode]) = {
    val ctx = new CookiePropertyProcessingContext(gateway, gateway.asInstanceOf[GatewayContext].locale);

    val cookie = gateway.getCookie()
    val cookieBean = new CompositeCookieBean(cookie)

    var updatedProperties: List[PropertyValue[AnyRef]] = Nil
    val reprAsm = new ElementRepresentationAssembler[PropertyValue[Nothing], CookiePropertyProcessingContext, PropertyUpdater[AnyRef]](
      updaters, updatersExt, ctx, (property, updaters) => {
        require(updaters.size == 1, "Expected exactly one updater")
        val updater: PropertyUpdater[_] = updaters(0)

        val keyNode = JsonNodeFactories.aJsonString(property.name)
        val valueNode = updatedNodesMap.get(keyNode)
        if (valueNode != null) {
          val updatedProperty: PropertyValue[_] = updater.updateProperty(valueNode).asInstanceOf[PropertyValue[_]]
          updatedProperties ::= updatedProperty.asInstanceOf[PropertyValue[AnyRef]]
        }
      })

    cookieBean.props().filter(new PropertiesFilter(ctx)).map(_.asInstanceOf[PropertyValue[Nothing]]).foreach(reprAsm)

    val updatedBean = cookieBean.update(updatedProperties)
    updatedBean.cookie
  }

  def getCookieMessageBuilder(gateway: ConfigPageGateway[_]) = {
    val ctx = new CookiePropertyProcessingContext(gateway, gateway.asInstanceOf[GatewayContext].locale);

    val cookie = gateway.getCookie()
    val cookieBean = new CompositeCookieBean(cookie)

    var fieldBuilders: List[JsonBuilderFactory[JsonRootNode]] = Nil

    val reprAsm = new ElementRepresentationAssembler[PropertyValue[Nothing], CookiePropertyProcessingContext, JsonBuilderFactory[JsonRootNode]](
      constructors, constructorsExt, ctx, (property, partBuilders) => {
        fieldBuilders ::= new CompositeJsonBuilderFactory(partBuilders)
      })

    cookieBean.props().filter(new PropertiesFilter(ctx)).map(_.asInstanceOf[PropertyValue[Nothing]]).foreach(reprAsm)

    new EditCookie(new EditContext(gateway.asInstanceOf[GatewayContext].contextName, gateway.id,
      gateway.canUnconsume()), fieldBuilders).createBuilder().get
  }

  def getProjectEntriesBuilder(project: Project with ProjectLocale, scenario: String): JsonNodeBuilder[JsonRootNode] = {
    val filteredEntries: List[TypedProjectEntry] = project.getEntries().filter(projectEntry => {
      if (scenario == null) {
        projectEntry.isPublic
      } else {
        projectEntry.isPublic && projectEntry.isInScenario(scenario)
      }
    }).asInstanceOf[List[TypedProjectEntry]]

    new ProjectEntries(getBuildersForProjectEntries(project, filteredEntries)).createBuilder().get
  }

  def getSelectedProjectEntriesBuilder(project: Project with ProjectLocale, entries: List[TypedProjectEntry]): JsonNodeBuilder[JsonRootNode] = {
    new ProjectEntries(getBuildersForProjectEntries(project, entries)).createBuilder().get
  }

  def getBuildersForProjectEntries(project: Project with ProjectLocale, entries: List[TypedProjectEntry]): List[JsonBuilderFactory[JsonRootNode]] = {
    val ctx = new ProjectEntriesBuildContext(project, this, project.locale)

    //var entryBuilders = Map.empty[String, CompositeJsonBuilderFactory]
    var entryBuilders: List[CompositeJsonBuilderFactory] = Nil
    val reprAsm = new ElementRepresentationAssembler[TypedProjectEntry, ProjectEntriesBuildContext, JsonBuilderFactory[JsonRootNode]](
      projectEntryConstructors, projectEntryConstructorsExt, ctx, (projectEntry, partBuilders) => {
        entryBuilders ::= new CompositeJsonBuilderFactory(partBuilders)
      })

    entries.foreach(reprAsm)

    entryBuilders
    //entryBuilders.asInstanceOf[Map[String, JsonBuilderFactory[JsonNode]]]

  }

}